<!--
* @fileName upload-file.vue
* @author caoke
* @date 2024/01/04 15:32:43
* @version V1.0.0
* @description 
!-->
<template>
  <div class="upload-imgs-container">
    <template v-for="(item) in itemList">
      <template v-if="item.display">
        <div class="thumb-item" :key="item.id" v-loading="item.loading">
          <van-image class="thumb-item-img" :src="item.display" :fit="fit" style="width: 100%; height: 100%;"/>
        </div>

      </template>
      <template v-else>
        <div class="upload-item" :key="item.id" :class="{ disabled: disabled }" :style="boxStyle"
          @click="handleClick(item.id)">
          <van-icon class="el-icon-plus" name="plus" />
        </div>
      </template>

    </template>
    <input class="upload-imgs__input" type="file" ref="input" @change="handleChange" :multiple="multiple"
      :accept="accept" />
  </div>
</template>

<script setup>
import { ref, onMounted, nextTick, computed } from 'vue'
import { createId, isEmptyObj } from "./utils";
const createItem = (data = null, oldData = {}) => {
  let item = {
    loading: false,
    id: createId(),
    status: "input", // new/edit/del/init/input
    src: "", // 图像相对地址
    display: "", // 图像完整地址, 用于显示
    imgId: "", // 图像资源 id
  };
  // 未传入data, 说明是单纯新建, 单纯新建的值是输入框状态
  if (!data) {
    return item;
  }
  // 判断是否是本地图片
  if (data.file && !data.id) {
    console.log("是本地图片");
    if (!isEmptyObj(oldData)) {
      // 如果旧数据状态是输入框, 则为新图片
      if (oldData.status === "input" || oldData.status === "new") {
        item.status = "new";
      }
      // 如果旧数据状态是初始化 init, 则为修改
      if (oldData.status === "init" || oldData.status === "edit") {
        item.status = "edit";
      }
    } else {
      item.status = "new";
    }
    // 本地数据初始化
    item.id = oldData.id || item.id;
    item.src = "";
    item.imgId = "";
    item.display = data.localSrc || item.display;
    item = Object.assign({}, data, item);
    return item;
  }
  // 存在id, 说明是传入已存在数据
  item.id = data.id || createId();
  item.imgId = data.imgId || item.imgId;
  item.src = data.src || item.src;
  item.display = data.display || item.display;
  item.status = data.status || "init";
  item = Object.assign({}, data, item);
  return item;

}
const props = defineProps({
  /** 每一项宽度 */
  width: {
    type: [Number, String],
    default: 80,
  },
  /** 每一项高度 */
  height: {
    type: [Number, String],
    default: 80,
  },
  /** 是否开启自动上传 */
  autoUpload: {
    type: Boolean,
    default: false,
  },
  /** 初始化数据 */
  value: {
    type: Array,
    default: () => [],
  },
  /** 接受的文件类型 */
  accept: {
    type: String,
    default: "image/*",
  },
  /** 最少图片数量 */
  minNum: {
    type: Number,
    default: 0,
  },
  /** 最多图片数量, 0 表示无限制 */
  maxNum: {
    type: Number,
    default: 0,
  },
  /** 是否可预览 */
  preview: {
    type: Boolean,
    default: true,
  },
  /** 是否可以一次多选 */
  multiple: {
    type: Boolean,
    default: false,
  },
  /** 图像验证规则 */
  rules: {
    type: [Object, Function],
    default: () => ({}),
  },
  /** 是否禁用, 禁用后只可展示 不可进行编辑操作, 包括: 新增, 修改, 删除, 改变顺序 */
  disabled: {
    type: Boolean,
    default: false,
  },
})
const input = ref('')
const itemList = ref([])
const currentId = ref('')// 正在操作项的id
const globalImgPriview = ref('$imagePreview')// 全局图片预览方法名
const percentage = ref(0)

const boxStyle = computed(() => {
  const { width, height, disabled } = props;
  const style = {};
  if (typeof width === "number") {
    style.width = `${width}px`;
  } else if (typeof width === "string") {
    style.width = width;
  }
  if (typeof height === "number") {
    style.height = `${height}px`;
  } else if (typeof height === "string") {
    style.height = height;
  }
  if (disabled) {
    style.cursor = "not-allowed";
  } else {
    style.cursor = "pointer";
  }
  /** 提示字体最大尺寸 */
  let fontSize = 12;
  /** 每行提示预设 */
  const maxText = 8;
  if (typeof width === "number" && width / maxText < fontSize) {
    fontSize = (width / maxText).toFixed(2);
  }
  style.fontSize = `${fontSize}px`;
  style.textAlign = "center";
  style.position = "relative";
  style.display = "flex";
  style.alignItems = "center";
  style.justifyContent = "center";
  style.overflow = "hidden";
  style.lineHeight = "1.3";
  style.flexDirection = "column";
  return style;
})

onMounted(() => {
  initItemList(props.value);
})
/**
 * 初始化 itemList
 * @param {Array} val 初始化的数据数组
 */
const initItemList = (val) => {
  const result = [];
  // 初始值不存在情况
  // 包括初始值不合法
  if (!val || !Array.isArray(val) || val.length === 0) {
    // TODO 固定数量图像上传, 直接初始化固定数量的上传控件
    result.push(createItem());
    itemList.value = result;
    return;
  }
}
/**
 * 选择图像文件后处理, 包括获取图像信息, 验证图像等
 * @param {Event} e input change 事件对象
 */
const handleChange = async (e) => {
  const { files } = e.target;
  console.log("选择图像文件后处理", files);
  let imgInfoList;
  if (!files) return;
  /** 中间步骤缓存, 在出错时用于释放 createObjectURL 的内存 */
  let cache = [];
  /**
   * 处理单个文件, 返回处理成功的文件数据
   * @param {File} file 文件
   */
  const handleImg = async (file) => {
    try {
      // 获取图像信息
      const info = await getImgInfo(file);
      cache.push(info);
      //TODO 验证图像信息
      // await this.validateImg(info)
      return info;
    } catch (error) {
      // 往外抛异常
      throw err;
    }
  };
  const asyncList = [];
  for (let i = 0; i < files.length; i += 1) {
    asyncList.push(handleImg(files[i]));
  }
  try {
    imgInfoList = await Promise.all(asyncList);
    console.log("返回处理成功的文件数据", imgInfoList);
    // 设置图片信息
    setImgInfo(imgInfoList, currentId.value);
    // 开启自动上传
    if (props.autoUpload) {
      itemList.value.forEach((ele) => {
        // this.uploadImg(ele);
      });
    }
  } catch (error) {
    console.log("err", error);
    // 清空缓存
    for (let i = 0; i < cache.length; i += 1) {
      window.URL.revokeObjectURL(cache[i].localSrc);
    }
    cache = null;
    console.error(err);
    this.$message.error(err.message);
  }
}
/**
 * 获取图像信息
 * @param {File} file 文件对象
 * @returns {LocalFileInfo} 信息对象
 */
const getImgInfo = async (file) => {
  const localSrc = window.URL.createObjectURL(file);
  // TODO 严格检测文件类型
  const fileType = "unknow";
  // TODO 检测是否是动图
  let isAnimated = null;
  return new Promise((resolve, reject) => {
    let image = new Image();
    image.src = localSrc;
    image.onload = () => {
      const localFileInfo = {
        localSrc,
        file,
        width: image.width,
        height: image.height,
        name: file.name,
        size: file.size,
        type: fileType === "unknow" ? file.type : fileType,
        lastModified: file.lastModified,
        isAnimated,
      };
      resolve(localFileInfo);
      image = null;
    };
    image.onerror = () => {
      // reject(new Error('图像加载失败'))
      // TODO 图片加载失败，展示默认图片
      const localFileInfo = {
        localSrc,
        file,
        width: "",
        height: "",
        name: file.name,
        size: file.size,
        type: fileType === "unknow" ? file.type : fileType,
        lastModified: file.lastModified,
        isAnimated,
      };
      resolve(localFileInfo);
      image = null;
    };
  });
}
/**
 * 根据信息列表设置图像信息
 * 用户选择图片, 图片通过验证后可获取到图像信息数组
 * 将这一组图像信息数据设置在 itemList 中
 * @param {Array<LocalFileInfo>} imgInfoList 需要设置的图像数组
 * @param {Number|String} id 操作项的 id
 */
const setImgInfo = (imgInfoList = [], currentId) => {
  // 找到特定图像位置
  const index = itemList.value.findIndex((item) => item.id === currentId);
  // 释放内存
  window.URL.revokeObjectURL(itemList.value[index].display);
  // 替换图片
  itemList.value[index] = createItem(imgInfoList[0], itemList.value[index])
  // TODO 如果需要设置的图像数量大于1, 需要执行追加图片逻辑
  // 初始化图片
  initItemList(itemList.value);
}
/**
  * 处理点击事件, 并转移到文件上传元素
  * 并记录当前操作元素 id
  * @param {Number|String} id 操作项的 id
  */
const handleClick = (id) => {
  if (!props.disabled) {
    currentId.value = id || "";
    input.value.value = null;
    input.value.click();
  }
}

</script>
<style lang='less' scoped>
.progress-container {
  padding: 8px;
  width: 100%;
}

.upload-imgs-container {
  display: flex;
  flex-wrap: wrap;

  .upload-item,
  .thumb-item {
    border: 1px dashed #d9d9d9;
    border-radius: 3px;
    transition: all 0.1s;
    color: #666666;
    margin-right: 1em;
    margin-bottom: 1em;

    &.disabled {
      color: #ababab;
    }

    &:not(.disabled):hover {
      border-color: #3963bc;
      color: #3963bc;
    }
  }

  .thumb-item {
    .info {
      display: flex;
      align-items: center;
      justify-content: center;
      position: absolute;
      width: 100%;
      height: 100%;
      top: 0;
      left: 0;
      transition: all 0.3s;
      transition-delay: 0.1s;

      .wait-upload {
        background: #ffcb71;
        color: white;
        position: absolute;
        display: inline-block;
        width: 1.7em;
        height: 1.5em;
        top: 0;
        right: 0;
        border-radius: 0 0 0 1.4em;
        transition: all 0.1s;

        &::before {
          font-size: 1.4em;
          position: absolute;
          right: -1px;
          transform: scale(0.7);
        }
      }
    }

    .progress {
      display: flex;
      align-items: center;
      justify-content: center;
      position: absolute;
      width: 100%;
      height: 100%;
      background-color: rgba(255, 255, 255, 1);
    }

    .control {
      display: flex;
      align-items: center;
      justify-content: center;
      position: absolute;
      width: 100%;
      height: 100%;
      top: 0;
      left: 0;
      opacity: 0;
      background-color: rgba(0, 0, 0, 0.3);
      transition: all 0.3s;
      transition-delay: 0.1s;

      .del {
        background: #f4516c;
        color: white;
        position: absolute;
        display: inline-block;
        width: 1.7em;
        height: 1.5em;
        top: 0;
        right: 0;
        opacity: 0;
        border-radius: 0 0 0 1.4em;
        transition: all 0.1s;

        &::before {
          font-size: 1.4em;
          position: absolute;
          right: -1px;
          transform: scale(0.7);
        }

        &:hover {
          transform: translate(-0.5em, 0.4em) scale(1.5);
        }
      }

      .preview {
        color: white;
        font-size: 2em;
        transition: all 0.2s;

        &:hover {
          transform: scale(1.2);
        }
      }

      .control-bottom {
        position: absolute;
        bottom: 0;
        left: 0;
        width: 100%;
        color: white;
        background-color: rgba(0, 0, 0, 0.3);
        font-size: 1.5em;
        display: flex;
        justify-content: space-around;
        transform: translate(0, 100%);
        transition: all 0.2s;
        transition-delay: 0.1s;
        padding: 5px 0;

        .control-bottom-btn {
          transform: all 0.2s;

          &.disabled {
            color: #ababab;
            cursor: not-allowed;
          }

          &:not(.disabled):hover {
            transform: scale(1.2);
          }
        }
      }
    }

    &:hover {
      .control {
        opacity: 1;
      }

      .del {
        opacity: 1;
      }

      .control-bottom {
        transform: translate(0, 0);
      }
    }
  }

  .upload-imgs__input {
    display: none;
  }
}
</style>